Створення моделі
================

Перш, ніж писати HTML код для форми, нам потрібно визначити, які дані ми
будемо отримувати від користувачів і яким правилам вони повинні відповідати.
Для фіксації цієї інформації можна використовувати клас моделі даних.
Модель даних, як говориться у роздiлi [Модель](/doc/guide/basics.model),
це центральне місце для зберігання та перевірки даних, які вводить користувач.

Залежно від того, яким чином використовуються дані вводу, ми можемо створити два типи моделі даних. 
Якщо ми отримуємо дані, оброблюємо їх, а потім видаляємо, то використовуємо [модель форми](/doc/guide/basics.model); 
якщо ж отримуємо дані та зберігаємо їх у базу даних, використовуємо [Active Record](/doc/guide/database.ar).
Обидва типи моделей даних використовують один і той же базовий клас [CModel], який визначає
загальний інтерфейс, використовуваний формою.

> Note|Примітка: У прикладах цього розділу використовуються моделі форми. Тим не менш,
все може бути в рівній мірі застосовано і до моделей [Active Record](/doc/guide/database.ar).

Визначення класу моделі
-----------------------

Нижче ми створимо клас моделі `LoginForm`, який буде використовуватися для
отримання даних, що вводяться користувачем на сторінці аутентифікації. Оскільки
ці дані використовуються виключно з метою аутентифікації користувача та
зберігати їх не потрібно, то створимо модель `LoginForm` як модель форми.

~~~
[php]
class LoginForm extends CFormModel
{
	public $username;
	public $password;
	public $rememberMe=false;
}
~~~

Ми оголосили у `LoginForm` три атрибути: `$username`, `$password` и `$rememberMe`.
Вони використовуються для зберігання імені користувача, пароля, а також значення опції зберігання
імені користувача. Так як `$rememberMe` за замовчуванням має значення `false`, то спочатку
у формі аутентифікації позначка цієї опції буде знята.

> Info|Інформація: Термін «атрибут» використовується для того, щоб відокремити властивості, 
які визначаються цими змінними-членами класу, від інших властивостей. 
Тут атрибут — це властивість, яка використовується в основному для зберігання даних, 
які вводить користувач, або даних, які одержуємо із бази даних.

Визначення правил перевірки
---------------------------

У момент, коли користувач відправляє дані форми, а модель їх отримує,
нам необхідно переконатися, що ці дані коректні, перш, ніж ми будемо їх використовувати.
Це здійснюється за допомогою перевірки даних відповідно до набору правил.
Правила перевірки задаються в методі `rules()`, який повертає масив сконфігурованих правил.

~~~
[php]
class LoginForm extends CFormModel
{
	public $username;
	public $password;
	public $rememberMe=false;

	private $_identity;

	public function rules()
	{
		return array(
			array('username, password', 'required'),
			array('rememberMe', 'boolean'),
			array('password', 'authenticate'),
		);
	}

	public function authenticate($attribute,$params)
	{
		$this->_identity=new UserIdentity($this->username,$this->password);
		if(!$this->_identity->authenticate())
			$this->addError('password','Невірне імʼя користувача або пароль.');
	}
}
~~~

У наведеному вище коді, `username` та `password` — обовʼязкові для заповнення поля,
поле `password` повинне бути перевірене також на відповідність зазначеному імені користувача.
Поле `rememberMe` може приймати значення `true` або `false`.

Кожне правило, яке повертає `rules()`, повинно бути задане у наступному форматі:

~~~
[php]
array('AttributeList', 'Validator', 'on'=>'ScenarioList', …додаткові параметри)
~~~

де `AttributeList` — рядок з іменами атрибутів, відокремлених комами, які повинні
бути перевірені у відповідності з правилами; `Validator` вказує на тип перевірки, що використовується;
параметр `on` — необовʼязковий параметр, що встановлює список сценаріїв, де повинно
застосовуватися правило; а також інші параметри — пари імʼя-значення, які використовуються для
ініціалізації значень [властивостей відповідних валідаторів](/doc/guide/basics.component).

Починаючи із версії 1.1.11, сценарії можна вносити до "чорних" списків.
Якщо ви не бажаєте виконувати валідацію для деякого правила, коли активні конкрентні сценарії,
ви можете визначити параметр `except`, що містить їхні імена.
Синтаксис такий же, як і для параметра `on`.

Перелік сценаріїв (параметри `on` та `except`) може бути визначений у двох різних формах,
які означають одне й те саме:

~~~
[php]
// довільний масив із іменами сценаріїв
'on'=>array('update', 'create'),
// рядок із іменами сценаріїв, розділених комами (пробіли ігноруються)
'except'=>'ignore, this, scenarios, at-all',
~~~

Є три способи вказати `Validator` у правилі перевірки. По-перше, `Validator` може бути іменем
метода у класі моделі даних, аналогічно `authenticate` у прикладі вище. Метод перевірки
оформлюється наступним чином:

~~~
[php]
/**
 * @param string $attribute імʼя поля, яке будемо валідувати
 * @param array $params додаткові параметри для правила валідації
 */
public function ValidatorName($attribute,$params) { … }
~~~

Другий спосіб — вказати `Validator` у якості імені класу. У цьому випадку для перевірки даних в момент 
застосування правила створюється екземпляр класу перевірки. Додаткові параметри в правилі використовуються для
ініціалізації значень атрибутів екземпляра. Клас перевірки повинен бути похідним класом від [CValidator].

Третій варіант — визначити псевдонім класу валідатора. У прикладі вище, імʼя
`required` — це псевдонім класу [CRequiredValidator], який перевіряє, щоб значення атрибута, яке перевіряється,
не було порожнім. Нижче наведений повний перелік наперед визначених псевдонімів валідаторов,
які входять до складу Yii:

   - `boolean`: псевдонім класу [CBooleanValidator], який перевіряє, щоб
атрибут мав значення або [CBooleanValidator::trueValue] або [CBooleanValidator::falseValue];

   - `captcha`: псевдонім класу [CCaptchaValidator], який перевіряє, щоб
значення атрибуту було рівним коду верифікації на [капчі](http://ru.wikipedia.org/wiki/Captcha);

   - `compare`: псевдонім класу [CCompareValidator], який перевіряє, щоб
значення атрибуту співпадало із значенням іншого атрибуту або константи;

   - `email`: псевдонім класу [CEmailValidator], який відповідає за перевірку коректності email адреси;

   - `date`: псевдонім класу [CDateValidator], який перевіряє, чи є атрибут коректної датою, часом або і тим і іншим.

   - `default`: псевдонім класу [CDefaultValueValidator], який присвоює значення
за замовчуванням обраним атрибутам;

   - `exist`: псевдонім класу [CExistValidator], який перевіряє наявність значення атрибута в зазначеному стовпці таблиці;

   - `file`: псевдонім класу [CFileValidator], який відповідає за перевірку атрибуту на
наявність в ньому імені завантаженого файлу;

   - `filter`: псевдонім класу [CFilterValidator], перетворюють атрибут з використанням фільтра;

   - `in`: псевдонім класу [CRangeValidator], який перевіряє, чи містяться дані у заданому наборі значень;

   - `length`: псевдонім класу [CStringValidator], який перевіряє, чи відповідає довжина даних заданому інтервалу;

   - `match`: псевдонім класу [CRegularExpressionValidator], який перевіряє дані на відповідність регулярному виразу;

   - `numerical`: псевдонім класу [CNumberValidator], який перевіряє, чи є дані коректним числовим значенням;

   - `required`: псевдонім класу [CRequiredValidator], який перевіряє, щоб значення атрибуту не було порожнім;

   - `type`: псевдонім класу [CTypeValidator], який перевіряє відповідність атрибута заданому типу даних;

   - `unique`: псевдонім класу [CUniqueValidator], який перевіряє, чи є дані унікальними в межах поля бази даних;

   - `url`: псевдонім класу [CUrlValidator], який відповідає за перевірку коректності URL.

Нижче представлені кілька прикладів використання наперед визначених валідаторов:

~~~
[php]
// імʼя користувача — обовʼязкове поле форми
array('username', 'required'),
// довжина імені користувача повинна бути від 3 до 12 символів включно
array('username', 'length', 'min'=>3, 'max'=>12),
// у сценарії реєстрації значення полів «password» та «password2» повинні бути рівні
array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
// у сценарії аутентифікації поле `password` повинно бути перевірене на відповідність вказаного імені користувача
array('password', 'authenticate', 'on'=>'login'),
~~~


Безпечне присвоювання значень атрибутів
----------------------------------------

Після того, як буде створений екземпляр моделі даних, нам часто потрібно заповнити його даними,
які ввів користувач. Це можна проробити легко та невимушено, використовуючи масове присвоювання:

~~~
[php]
$model=new LoginForm;
if(isset($_POST['LoginForm']))
	$model->attributes=$_POST['LoginForm'];
~~~

Останній вираз у прикладі як раз і є масовим присвоюванням, де значення кожної змінної у `$_POST['LoginForm']`
присвоюється відповідному атрибуту моделі. Це еквівалентно наступної операції:

~~~
[php]
foreach($_POST['LoginForm'] as $name=>$value)
{
	if($name є безпечним атрибутом)
		$model->$name=$value;
}
~~~

Дуже важливо визначити які атрибут є безпечними. Наприклад, якщо ми зробимо первинний ключ таблиці безпечним,
зловмисник зможе отримати шанс його змінити та, таким чином, змінити дані, які він не повинен змінювати, 
оскільки не авторизований для цього.

### Оголошення безпечних атрибутів

Атрибут вважається безпечним, якщо він зʼявляється в правилі валідації, вживаному в даному сценарії. Наприклад,

~~~
[php]
array('username, password', 'required', 'on'=>'login, register'),
array('email', 'required', 'on'=>'register'),
~~~

У коді вище атрибути `username` та `password` потрібні у сценарії `login`,
а атрибути `username`, `password` та `email` — у сценарії `register`. В результаті, 
якщо ми виконуємо масове присвоювання у сценарії `login`, то тільки атрибути 
`username` та `password` будуть масово присвоєні, так як тільки вони входять
в правило валідації для сценарія `login`. З іншої сторони, якщо поточним сценарієм є `register`, 
то всі три атрибути можуть бути масово присвоєні.

~~~
[php]
// сценарій входу
$model=new User('login');
if(isset($_POST['User']))
	$model->attributes=$_POST['User'];

// сценарій реєстрації
$model=new User('register');
if(isset($_POST['User']))
	$model->attributes=$_POST['User'];
~~~

Так чому ж ми використовуємо саме таку політику для визначення, чи є атрибут безпечним чи ні? 
Якщо атрибут вже є в одному або декількох правилах валідації, навіщо турбуватися про щось ще?

Важливо памʼятати, що правила валідації використовуються для перевірки введених користувачем даних, 
а не даних, які ми генеруємо в коді (наприклад, поточний час або автоматично згенерований первинний ключ). 
Тому, НЕ ДОДАВАЙТЕ правила валідації для атрибутів, які не є введеними кінцевим користувачем.

Іноді ми хочемо оголосити атрибут безпечним навіть якщо в дійсності не маємо правила для нього. 
Приклад - атрибут змісту статті, який може приймати будь-які дані, введені користувачем. 
Для цього ми можемо використовувати спеціальне правило `safe`:

~~~
[php]
array('content', 'safe')
~~~

Для повноти картини, існує також правило `unsafe`, яке використовується для явної вказівки небезпечного атрибута:

~~~
[php]
array('permission', 'unsafe')
~~~

Правило `unsafe` використовується рідко і є протилежністю описаного нами раніше визначенням безпечних атрибутів.

У разі небезпечних вхідних даних, ми повинні привласнювати значення відповідним атрибутам, 
використовуючи окремі операції привласнення, як представлено нижче:

~~~
[php]
$model->permission='admin';
$model->id=1;
~~~


Виконання перевірки
-------------------

Як тільки модель заповнена даними користувача, викликаємо метод [CModel::validate()], 
щоб запустити процес перевірки. За підсумками перевірки метод повертає позитивний або негативний результат. 
Для моделей [CActiveRecord] перевірку можна виконувати автоматично в момент виклику методу [CActiveRecord::save()].

Ми можемо прозначити сценарій через властивість [scenario|CModel::scenario] і, з його допомогою, 
задати, який набір правил використовувати для перевірки.

Перевірка виконується в залежності від сценарію. Властивість [scenario|CModel::scenario] задає сценарій, 
у якому застосовується модель і визначає який набір правил валідації використовувати. 
Приміром, у сценарії `login`, ми хочемо перевірити лише поля моделі користувача `username` і `password`. 
У сценарії `register` нам необхідно перевіряти більшу кількість даних: `email`, `address` і інших. Нижче показано, як провести перевірку для сценарію `register`:

~~~
[php]
// створюємо модель User і задаємо її сценарій як `register`. Вираз нижче еквівалентний наступному:
// $model=new User;
// $model->scenario='register';
$model=new User('register');

// наповнюємо модель даними
$model->attributes=$_POST['User'];

// проводимо валідацію
if($model->validate())   // якщо дані вірні
    …
else
    …
~~~

Сценарій для правил перевірки задається у властивості `on` правила. 
Якщо `on` не визначено, правило використовується для всіх сценаріїв. Наприклад,

~~~
[php]
public function rules()
{
	return array(
		array('username, password', 'required'),
		array('password_repeat', 'required', 'on'=>'register'),
		array('password', 'compare', 'on'=>'register'),
	);
}
~~~

Перше правило буде поширюватися на будь-які сценарії, 
а два наступних будуть застосовуватися тільки до сценарію `register`.

Інформація про помилки
----------------------

Після перевірки всі можливі помилки знаходяться в обʼєкті моделі. Ми можемо отримати їх через 
[CModel::getErrors()] та [CModel::getError()]. Перший метод повертає всі помилки для зазначеного атрибута моделі, 
другий - тільки першу помилку.

Щоб дізнатися, чи виникли під час виконання перевірки будь-які помилки, можна скористатися методом 
[CModel::hasErrors()]. І якщо помилки дійсно є, то отримати їх можна за допомогою методу 
[CModel::getErrors()]. Обидва методи можуть бути використані як для всіх, так і для конкретного атрибуту.

Мітки атрибутів
---------------

Часто при роботі з формами для кожного поля потрібно відображати його позначку. 
Вона підказує користувачеві, які дані йому потрібно ввести в поле. 
Ми, звичайно, можемо задати мітки полів у представленні, але, якщо вказати їх безпосередньо в моделі даних, 
ми виграємо в зручності і гнучкості.

За замовчуванням [CModel] у якості міток повертає назви атрибутів. 
Змінити їх можна, якщо перевизначити метод [attributeLabels()|CModel::attributeLabels].

Далі ми побачимо, що можливість вказівки міток у моделі даних дозволяє швидко створювати складні форми.